package net.cathackers.devel.scmp.chat.impl.xmpp.presence;

import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;

import net.cathackers.devel.exceptions.CATException;

import net.cathackers.devel.scmp.auth.interfaces.UserID;

import net.cathackers.devel.scmp.chat.exceptions.RosterItemNotFoundException;
import net.cathackers.devel.scmp.chat.impl.xmpp.iqcomponent.roster.RosterItem;
import net.cathackers.devel.scmp.chat.impl.xmpp.iqcomponent.roster.RosterProcessor;
import net.cathackers.devel.scmp.chat.impl.xmpp.messageComponent.OfflineMessage;
import net.cathackers.devel.scmp.chat.impl.xmpp.messageComponent.OfflineMessageStore;
import net.cathackers.devel.scmp.chat.interfaces.ChatManager;
import net.cathackers.devel.scmp.chat.interfaces.ChatSession;
import net.cathackers.devel.scmp.chat.interfaces.RosterSubscriptionManager;
import net.cathackers.devel.scmp.chat.interfaces.RosterSubscriptionManager.Ask;
import net.cathackers.devel.scmp.chat.interfaces.RosterSubscriptionManager.Subscription;

import net.cathackers.devel.scmp.exceptions.BackendException;
import net.cathackers.devel.scmp.factories.SCMPFactory;
import net.cathackers.devel.scmp.xmpp.IQType;
import net.cathackers.devel.scmp.xmpp.PresencePacket;
import net.cathackers.devel.scmp.xmpp.RosterPacket;
import net.cathackers.devel.scmp.xmpp.PresencePacket.PresenceType;

public class PresenceHandler {
    private Logger logger_;
    private RosterSubscriptionManager roster_;
    private ChatManager manager_;
    private OfflineMessageStore offlineStore_;
    private RosterProcessor rosterProcessor_;

    public PresenceHandler(RosterSubscriptionManager r) {
	roster_          = r;
	logger_          = SCMPFactory.getLogger(this);
	manager_         = SCMPFactory.getChatManager();
	offlineStore_    = SCMPFactory.getOfflineMessageStore();
	rosterProcessor_ = new RosterProcessor();

    }
    /**
     * This method gets user presence updates
     * the updates are forwarded to the recipients 
     * @param presence
     */

    public void handleAvailablePresence(PresencePacket presence) {
	UserID user = presence.getFrom();
	ChatSession session = manager_.getChatSession(presence
		.getBaseStreamID());
	if (user == null) {
	    user = session.getUserID();
	}
	logger_
		.debug("the Presence Component received an available Presence from  "
			+ user.getCanonicalName());
	if (null == presence.getTo()) {
	    if (!session.isInitializedPresence()) {
		this.initializePresence(session);
		this.restoreOfflineMessages(user);
	    }
	    this.broadcastPresence(presence);
	} else {
	  //  if (manager_.getSubscribers(session.getUserID()).contains(presence.getTo())) {
		logger_.debug("forward the presence directly to the recipient");
		this.forwardPresence(presence);
	 //   }
	}

    }

    /**
     * The server automatically forwards these updates to all of the presence
     * subscribers of this user
     * 
     * @param PresencePacket
     */

    public void initializePresence(ChatSession session) {
        UserID sender = session.getUserID();
	logger_.debug("register my online subscribers");
	try {
	    UserID[] subscribers = roster_.getSubscribers(sender);
	    logger_.info("the number of subscribers is " + subscribers.length);
	    for (UserID user : subscribers) {
		boolean isOnline = manager_.checkIfSessionOnline(user
			.getGeneralUserID());
		if (isOnline) {
		    manager_.addSubscriber(sender, user.getGeneralUserID());
		    logger_.debug("Adding the subscriber  " + user.getID()
			    + " to " + sender.getID() + " session");
		}
	    }
	    
	   //UserID[] publishers = roster_.getPublishers(sender);
	    // I did this to improve the performance
	      UserID[] publishers = subscribers;
	    logger_.debug("register myself in my online publishers object " + publishers.length);
	    for (UserID i : publishers) {
		manager_.addSubscriber(i, sender.getGeneralUserID());
		for (ChatSession c : manager_.getChatSession(i)) {    
		    if (c.isInitializedPresence() == false) {
			PresencePacket presence = new PresencePacket(
				PresenceType.AVAILABLE);
			presence.setTo(sender);
			presence.setFrom(c.getUserID());
			this.forwardPresence(presence);
		    } else {
			PresencePacket packet = c.getPresenceInformation()
				.getPacket();
			packet.setTo(sender);
			packet.setFrom(c.getUserID());
			this.forwardPresence(packet);
		    }
		}
	    }
	} catch (BackendException e) {
	    logger_.error("Could not Connect To Database ");
	    logger_.error(e.getMessage());
	} catch (RosterItemNotFoundException e) {
	    logger_.error("Could not Find A roster For this User");
	    logger_.error(e.getMessage());
	}
    }

    /**
     * This method sends people presences that subscriber is interested in their
     * presences to the subscriber
     * 
     * @throws CATException
     */

    public void broadcastPresence(PresencePacket presence) {

	ChatSession session = manager_.getChatSession(presence
		.getBaseStreamID());
	UserID sender = null;
	if (presence.getFrom() == null) {
	    sender = session.getUserID();
	    presence.setFrom(sender);
	} else {
	    sender = presence.getFrom();
	}

	if (presence.getPresenceType().equals(PresenceType.AVAILABLE)) {
	    PresenceInformation presenceInfo = session.getPresenceInformation();
	    presenceInfo.setPacket(presence);
	    session.setPresence(presenceInfo);
	    if (!session.isInitializedPresence()) {
		session.setInitializedPresence(true);
	    }
	}
	
	    Set<UserID> subscribers = manager_.getSubscribers(sender);
	    if (subscribers == null) {
		 logger_.info("all subscribers are offline");
		return;
	    }
	    logger_.info("the number of online subscribers is "
		    + subscribers.size());
	    for (Iterator<UserID> i = subscribers.iterator(); i.hasNext();) {
		PresencePacket copyOfPresence = new PresencePacket(presence.getDocument());
		copyOfPresence.setTo(i.next());
		logger_.debug("forward the presence packet to  "
			+ copyOfPresence.toXML());
		this.forwardPresence(copyOfPresence);
	    }
	
	if (presence.getPresenceType().equals(PresenceType.UNAVAILABLE)) {
	    for (Iterator<UserID> i = subscribers.iterator(); i.hasNext();) {
		manager_.removeSubscriber(i.next(), sender);
	    }
	    manager_.removeSubscribersCacheElement(sender);
	}

    }
    /**
     * This method manages a request of  subscription to a user’s presence
     * Send: <presence from='Amal'
     * Send:           to='Eman'
     * Send:           type='subscribe'/>
     * Recv: <presence from='Eman'
     * Recv:           to='Amal'
     * Recv:           type='subscribed'/>
     * @param presence
     */

    public void handleSubscribePresence(PresencePacket presence) {
	UserID recipient = null;
	UserID sender;
	RosterItem item = null;
	sender = manager_.getChatSession(presence.getBaseStreamID())
		.getUserID();
	recipient = presence.getTo();
	try {
	    logger_
		    .debug("the Presence Component received an subscribe Presence from  "
			    + sender.getID());
	    recipient = presence.getTo();
	    if (recipient != null) {
		item = roster_.getRosterItem(sender, recipient);
		logger_.debug("the sender is " + sender.getID()
			+ " the recipient is " + recipient.getID());
		if (item != null) {
		    Subscription sub = item.getSubscription();
		    presence.setFrom(sender.getGeneralUserID());
		    presence.setTo(recipient.getGeneralUserID());
		    if (sub.equals(Subscription.NONE)) {
			this.forwardPresence(presence);
		    }
		    if (sub.equals(Subscription.FROM)) {
			PresencePacket subscribedPacket = new PresencePacket(PresenceType.SUBSCRIBED);
			subscribedPacket.setTo(sender.getGeneralUserID());
			subscribedPacket.setFrom(recipient);
			this.handleSubscribedPresence(subscribedPacket);
		    }
		   
		}

	    }
	} catch (RosterItemNotFoundException ex) {
	    item = new RosterItem(recipient.getGeneralUserID(), recipient
		    .getID(), Ask.NONE, Subscription.NONE, "scmp");
	    this.pushRosterUpdate(sender, presence.getBaseStreamID(), item);
	    this.forwardPresence(presence);

	} catch (BackendException e) {
	    logger_.error("Could not Connect To Database ");
	    logger_.error(e.getMessage());
	}

    }

    /**
     * restore offline messages and send it to the sender
     * 
     * @param sender
     */
    public void restoreOfflineMessages(UserID sender) {
	logger_.debug("get the offline messages of  " + sender.getID());
	List<OfflineMessage> messages = offlineStore_.getOfflineMessage(sender
		.getGeneralUserID());
	logger_.debug("get the offline messages of  " + messages.size());
	for (Iterator<OfflineMessage> i = messages.iterator(); i.hasNext();) {
	    OfflineMessage msg = i.next();
	    logger_.debug("Delivering the offline messages of  " + msg.toXML());
	    manager_.deliverToUser(msg);
	}

    }

    /**
     * The server forwards this packet directly to the recipient
     * 
     * @param PresencePacket
     */

    public void forwardPresence(PresencePacket packet) {
	logger_.debug("forward this presence directly to the recipient  "
		+ packet.toXML());
	boolean isOnline = manager_.checkIfSessionOnline(packet.getTo().getGeneralUserID());
	if (isOnline == true) {
	    logger_.debug("the recipient is online");
	    manager_.deliverToUser(packet);
	} else {
	    if (packet.getPresenceType().equals(PresenceType.SUBSCRIBE)) {
	    logger_.debug("the presence will be stored offline");
	    offlineStore_.storeOfflineMessage(packet);
	    } else {
		logger_.debug("this packet can not be stored");
	    }
	}
	
    }
    /**
     * This method manages a request of  removal of an existing 
     * presence subscription
     * Send: <presence from='Amal'
     * Send:           to='Eman'
     * Send:           type='UnSubscribe'/>
     * Recv: <presence from='Eman'
     * Recv:           to='Amal'
     * Recv:           type='UnSubscribed'/>

     * @param packet
     */

    public void handleUnSubscribePresence(PresencePacket packet) {
	try {
	    ChatSession session = manager_.getChatSession(packet
		    .getBaseStreamID());
	    UserID sender = packet.getFrom();

	    if (sender == null) {
		sender = session.getUserID();
	    }
	    logger_
		    .debug("the Presence Component received an unsubscribe Presence from  "
			    + sender.getID());
	    UserID recipient = packet.getTo();
	    RosterItem recipientRosterItem = roster_.getRosterItem(recipient,
		    sender);
	    if (recipientRosterItem != null) {
		Subscription sub = recipientRosterItem.getSubscription();
		if (sub.equals(Subscription.BOTH)) {
		recipientRosterItem.setAsk(Ask.NONE);
		recipientRosterItem.setSubscription(Subscription.TO);
		}
		
		if (sub.equals(Subscription.FROM)) {
			recipientRosterItem.setAsk(Ask.NONE);
			recipientRosterItem.setSubscription(Subscription.NONE);
	        }
		this.pushRosterUpdate(recipient, null, recipientRosterItem);
		packet.setFrom(sender.getGeneralUserID());
		packet.setTo(recipient.getGeneralUserID());
		this.forwardPresence(packet);
		PresencePacket unsubscribedPacket = new PresencePacket(PresenceType.UNSUBSCRIBED);
		unsubscribedPacket.setFrom(recipient);
		unsubscribedPacket.setTo(sender);
		this.forwardUnAvailablePresence(recipient, sender)
;		
	    }

	    RosterItem item = roster_.getRosterItem(sender, recipient);
	    if (item != null) {
		Subscription sub = item.getSubscription();
		packet.setFrom(sender.getGeneralUserID());
		if (sub.equals(Subscription.TO)) {
		    item.setAsk(Ask.NONE);
		    item.setSubscription(Subscription.NONE);
		}
		if (sub.equals(Subscription.BOTH)) {
		    item.setAsk(Ask.NONE);
		    item.setSubscription(Subscription.FROM);
		}
		this.pushRosterUpdate(sender, session.getSessionID(), item);
		this.forwardPresence(packet);

	    }
	} catch (BackendException e) {
	    logger_.error(e.getMessage());
	} catch (RosterItemNotFoundException e) {
	    logger_.error("Could not Find A roster For this User");
	    logger_.error(e.getMessage());
	}

    }
    /**
     * Subscription to the sender’s presence has been accepted
     * @param packet
     */

    public void handleSubscribedPresence(PresencePacket packet) {
	RosterItem item;
	RosterItem recipientRosterItem = null;
	ChatSession session = manager_.getChatSession(packet.getBaseStreamID());
	UserID sender = packet.getFrom();
	if (sender == null) {
	    sender = session.getUserID();
	}
	logger_
		.debug("the Presence Component received an subscribed Presence from  "
			+ sender.getID());
	UserID recipient = packet.getTo();
	try {
	    logger_.debug("getting the RosterItem of the sender");
		recipientRosterItem = roster_.getRosterItem(recipient
			.getGeneralUserID(), sender.getGeneralUserID());

	    logger_.debug("getting the RosterItem of the recipient");
	    item = roster_.getRosterItem(sender, recipient);
	    if (null != item) {
		Subscription sub = item.getSubscription();
		if (sub.equals(Subscription.TO)) {
		    item.setAsk(Ask.NONE);
		    item.setSubscription(Subscription.BOTH);

		}
		if (sub.equals(Subscription.NONE)) {
		    item.setAsk(Ask.NONE);
		    item.setSubscription(Subscription.FROM);
		    if (item.getNickname() == "" || item.getNickname() == null) {
			item.setNickname(item.getJID().getID());
		    }

		}
		this.pushRosterUpdate(sender, packet.getBaseStreamID(), item);
		packet.setFrom(sender.getGeneralUserID());
		packet.setTo(recipient.getGeneralUserID());
		this.forwardPresence(packet);
		this.forwardAvailablePresence(sender, recipient
			.getGeneralUserID());
		
	    }

	} catch (BackendException e) {
	    logger_.error("Could not Connect To Database ");
	    logger_.error(e.getMessage());
	} catch (RosterItemNotFoundException e) {
	    logger_.error("Could not Find A roster For this User");
	    logger_.error(e.getMessage());
          
	    item = new RosterItem(recipient.getGeneralUserID(), recipient
		    .getID(), Ask.NONE, Subscription.FROM, "scmp");
	    this.pushRosterUpdate(sender, session.getSessionID(), item);
	    packet.setFrom(sender.getGeneralUserID());
	    this.forwardPresence(packet);

	}
	
	if (recipientRosterItem != null) {
	    Subscription sub = recipientRosterItem.getSubscription();
	    logger_.debug("the subscription = " + sub.getStringValue());
	    
	    if (sub.equals(Subscription.NONE)) {
		recipientRosterItem.setAsk(Ask.NONE);
		recipientRosterItem.setSubscription(Subscription.TO);

	    } else if (sub.equals(Subscription.FROM)) {
		recipientRosterItem.setAsk(Ask.NONE);
		recipientRosterItem.setSubscription(Subscription.BOTH);

	    } else {
		logger_
			.debug("silently ignore the presence stanza of type subscribed");
		return;
	    }
	    this.pushRosterUpdate(recipient, null, recipientRosterItem);
	    packet.setFrom(sender.getGeneralUserID());
	    packet.setTo(recipient.getGeneralUserID());
	    this.forwardPresence(packet);
	    this.forwardAvailablePresence(recipient, sender.getGeneralUserID());
	 
	}

    }

    /**
     * deliver the available presence stanza received from each of the contact's
     * available resources to each of the recipient's available resources
     * 
     * @param sender
     * @param recipient
     */
    private void forwardAvailablePresence(UserID sender, UserID recipient) {
	ChatSession[] senderSessions = manager_.getChatSession(sender
		.getGeneralUserID());
	ChatSession[] recipientSessions = manager_.getChatSession(recipient
		.getGeneralUserID());
	for (ChatSession session : senderSessions) {
	    UserID from = null;
	    if (session.getPresenceInformation().isAvailable()) {
		from = session.getUserID();
	    } else {
		continue;
	    }
	    for (ChatSession recipientSession : recipientSessions) {
		UserID to = null;
		if (recipientSession.getPresenceInformation().isAvailable()) {
		    to = recipientSession.getUserID();
		} else {
		    continue;
		}
		PresencePacket presence = session.getPresenceInformation()
			.getPacket();
		presence.setFrom(from);
		presence.setTo(to);
		this.forwardPresence(presence);

	    }

	}

    }
    /**
     * Subscription to the sender’s presence has been removed
     * @param presence
     */

    public void handleUnSubscribedPresence(PresencePacket presence) {
	RosterItem item;
	RosterItem recipientRosterItem = null;
	ChatSession session = manager_.getChatSession(presence
		.getBaseStreamID());
	UserID sender = presence.getFrom();
	if (sender == null) {
	    sender = session.getUserID();
	}
	logger_
		.debug("the Presence Component received an unsubscribed Presence from  "
			+ sender.getID());
	UserID recipient = presence.getTo();
	
	try {
	    recipientRosterItem = roster_.getRosterItem(recipient, sender);
	    item = roster_.getRosterItem(sender, recipient);
	    
	    if (item != null) {
		Subscription sub = item.getSubscription();
		presence.setFrom(sender.getGeneralUserID());
		
		if (sub.equals(Subscription.FROM)) {
		    item.setAsk(Ask.NONE);
		    item.setSubscription(Subscription.NONE);

		} else if (sub.equals(Subscription.BOTH)) {
		    item.setAsk(Ask.NONE);
		    item.setSubscription(Subscription.TO);

		} else {
		    logger_
			    .debug("silently ignore the presence stanza of type unsubscribed");
		    return;
		}
		this.pushRosterUpdate(sender, session.getSessionID(), item);
		this.forwardPresence(presence);
		this.forwardUnAvailablePresence(sender, recipient);
	    }
	} catch (BackendException e) {
	    logger_.error("Could not Connect To Database ");
	    logger_.error(e.getMessage());
	} catch (RosterItemNotFoundException e) {
	    logger_.error("Could not Find A roster For this User");
	    logger_.error(e.getMessage());
	}
	if (recipientRosterItem != null) {
	    Subscription sub = recipientRosterItem.getSubscription();
	    logger_.debug("the subscription = " + sub.getStringValue());
	    
	    if (sub.equals(Subscription.TO)) {
		recipientRosterItem.setAsk(Ask.NONE);
		recipientRosterItem.setSubscription(Subscription.NONE);

	    } else if (sub.equals(Subscription.BOTH)) {
		recipientRosterItem.setAsk(Ask.NONE);
		recipientRosterItem.setSubscription(Subscription.FROM);

	    } else if (sub.equals(Subscription.NONE)) {
		recipientRosterItem.setAsk(Ask.NONE);
	    }

	    else {
		logger_
			.debug("silently ignore the presence stanza of type subscribed");
		return;
	    }
	    this.pushRosterUpdate(recipient, null, recipientRosterItem);
	    presence.setFrom(sender.getGeneralUserID());
	    presence.setTo(recipient.getGeneralUserID());
	    this.forwardPresence(presence);
	    this.forwardUnAvailablePresence(sender, recipient
		    .getGeneralUserID());
	}
    }
    /**
     * deliver the Unavailable presence stanza received from each of the contact's
     * available resources to each of the recipient's available resources
     * @param sender
     * @param recipient
     */

    private void forwardUnAvailablePresence(UserID sender, UserID recipient) {
	PresencePacket presence = new PresencePacket(PresenceType.UNAVAILABLE);
	presence.setFrom(recipient);
	presence.setTo(sender.getGeneralUserID());
	this.forwardPresence(presence);

    }
    /**
     * Deliver the updates
     * @param user
     * @param streamID
     * @param item
     */

    private void pushRosterUpdate(UserID user, String streamID, RosterItem item) {
	RosterPacket roster = new RosterPacket(IQType.set);
	roster.setFrom(user.getGeneralUserID());
	roster.addRosterItem(item);
	roster.setBaseStreamID(streamID);
	roster.setID("");
	logger_.debug("push the RosterPacket to the router  " + roster.toXML());
	rosterProcessor_.updateRoster(roster);
	

	

    }

}
